package com.im.tcp.handler;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.im.codec.pack.LoginPack;
import com.im.codec.pack.message.ChatMessageAck;
import com.im.codec.pack.user.LoginAckPack;
import com.im.codec.pack.user.UserStatusChangeNotifyPack;
import com.im.codec.proto.Message;
import com.im.codec.proto.MessagePack;
import com.im.tcp.config.TcpConfig;
import com.im.common.constant.Constants;
import com.im.common.enums.ImConnectStatusEnum;
import com.im.common.enums.command.GroupEventCommand;
import com.im.common.enums.command.MessageCommand;
import com.im.common.enums.command.SystemCommand;
import com.im.common.enums.command.UserEventCommand;
import com.im.common.model.RestResponse;
import com.im.common.model.UserClientDto;
import com.im.common.model.UserSession;
import com.im.common.model.message.CheckSendMessageReq;
import com.im.tcp.feign.FeignMessageService;
import com.im.tcp.publish.MqMessageProducer;
import com.im.tcp.utils.SessionSocketHolder;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RMap;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;

import java.net.InetAddress;

/**
 * 服务器的处理程序类
 */
@Slf4j
public class NettyServerHandler extends SimpleChannelInboundHandler<Message> {

    private TcpConfig nettyConfig;
    private RedissonClient redissonClient;
    private MqMessageProducer mqMessageProducer;
    private FeignMessageService feignMessageService;

    public NettyServerHandler(RedissonClient redissonClient, FeignMessageService feignMessageService, TcpConfig nettyConfig, MqMessageProducer mqMessageProducer) {
        this.redissonClient = redissonClient;
        this.nettyConfig = nettyConfig;
        this.feignMessageService = feignMessageService;
        this.mqMessageProducer = mqMessageProducer;
    }

    /**
     * 处理接收到的消息
     *
     * @param ctx ChannelHandlerContext对象，表示通道处理的上下文环境
     * @param msg 表示接收到的消息
     * @throws Exception
     */
    protected void channelRead0(ChannelHandlerContext ctx, Message msg) throws Exception {
        // 获取消息指令
        Integer command = msg.getMessageHeader().getCommand();
        // 判断消息指令是否为登录指令
        if (command == SystemCommand.LOGIN.getCommand()) {
            // 将消息解析成登录数据包
            LoginPack loginPack = JSON.parseObject(JSON.toJSONString(msg.getMessagePack()), new TypeReference<LoginPack>() {
            }.getType());
            // 获取用户id、应用id和客户端类型
            String userId = loginPack.getUserId();
            Integer appId = msg.getMessageHeader().getAppId();
            Integer clientType = msg.getMessageHeader().getClientType();
            String imei = msg.getMessageHeader().getImei();
            // 将用户ID、应用ID和客户端类型存储到Channel的属性中
            ctx.channel().attr(AttributeKey.valueOf(Constants.UserId)).set(userId);
            ctx.channel().attr(AttributeKey.valueOf(Constants.AppId)).set(appId);
            ctx.channel().attr(AttributeKey.valueOf(Constants.ClientType)).set(clientType);
            ctx.channel().attr(AttributeKey.valueOf(Constants.Imei)).set(imei);

            // 创建用户会话对象
            UserSession userSession = new UserSession();
            userSession.setAppId(appId);
            userSession.setClientType(clientType);
            userSession.setUserId(userId);
            userSession.setConnectState(ImConnectStatusEnum.ONLINE_STATUS.getCode());
            userSession.setBrokerId(nettyConfig.getBrokerId());
            userSession.setImei(msg.getMessageHeader().getImei());
            try {
                InetAddress localHost = InetAddress.getLocalHost();
                userSession.setBrokerHost(localHost.getHostAddress());
            } catch (Exception e) {
                e.printStackTrace();
            }

            // 存储用户会话到Redis
            RMap<String, String> map = redissonClient.getMap(appId + Constants.RedisConstants.UserSessionConstants + userId);
            map.put(clientType + ":" + imei, JSON.toJSONString(userSession));

            // 将用户ID和通道对象绑定到自定义的SessionSocketHolder中
            SessionSocketHolder.put(appId, userId, clientType, imei, (NioSocketChannel) ctx.channel());

            // 用户上线的相关信息，发送给其他服务端
            UserClientDto dto = new UserClientDto();
            dto.setImei(imei); // 设置imei号
            dto.setClientType(clientType); // 设置客户端类型
            dto.setAppId(appId); // 设置应用ID
            dto.setUserId(userId); // 设置用户ID
            // 通过redissonClient获取一个RTopic对象，并指定频道为UserLoginChannel
            RTopic topic = redissonClient.getTopic(Constants.RedisConstants.UserLoginChannel);
            // 记录日志
            log.info("发送用户上线通知：{}", JSON.toJSONString(dto));
            // 将dto对象序列化为JSON格式，并发送到UserLoginChannel频道中
            topic.publish(JSON.toJSONString(dto));

            // 用户状态变更的相关信息，发送给消息队列
            UserStatusChangeNotifyPack pack = new UserStatusChangeNotifyPack();
            pack.setAppId(msg.getMessageHeader().getAppId());
            pack.setUserId(loginPack.getUserId());
            pack.setStatus(ImConnectStatusEnum.ONLINE_STATUS.getCode());
            // 发送用户状态变更消息
            Object o = mqMessageProducer.sendMessage(pack, msg.getMessageHeader(), UserEventCommand.USER_ONLINE_STATUS_CHANGE.getCommand());
            log.info("发送用户状态变更通知：{}", o);

            // 给客户端回复登录ack
            MessagePack<LoginAckPack> loginSuccess = new MessagePack<>();
            // 设置 userId 属性为登录请求包中的 userId
            LoginAckPack loginAckPack = new LoginAckPack();
            loginAckPack.setUserId(loginPack.getUserId());
            // 设置 MessagePack 对象的命令为登录成功的命令，并将 LoginAckPack 对象设置为数据
            loginSuccess.setCommand(SystemCommand.LOGINACK.getCommand());
            // 设置命令描述信息
            loginSuccess.setDesc(SystemCommand.LOGINACK.getDesc());
            loginSuccess.setData(loginAckPack);
            // 设置 MessagePack 对象的 IMEI 和 AppId 属性为收到的消息的对应属性
            loginSuccess.setImei(msg.getMessageHeader().getImei());
            loginSuccess.setAppId(msg.getMessageHeader().getAppId());
            // 将 MessagePack 对象写入并刷新到通道中
            ctx.channel().writeAndFlush(loginSuccess);
        } else if (command == SystemCommand.LOGOUT.getCommand()) {
            // 从系统中移除用户会话，包括从SessionSocketHolder、Redis中移除会话并发送用户状态更改通知
            SessionSocketHolder.removeUserSession((NioSocketChannel) ctx.channel());
        } else if (command == SystemCommand.PING.getCommand()) {
            ctx.channel().attr(AttributeKey.valueOf(Constants.ReadTime)).set(System.currentTimeMillis());
        } else if (command == MessageCommand.MSG_P2P.getCommand() || command == GroupEventCommand.MSG_GROUP.getCommand()) { // 如果消息的命令编号为 MSG_P2P 或 MSG_GROUP，则表示发送点对点消息或群组消息
            try {
                // 获取消息的发送者和接收者 ID，并创建 CheckSendMessageReq 对象
                String toId = "";
                CheckSendMessageReq req = new CheckSendMessageReq();
                req.setAppId(msg.getMessageHeader().getAppId());
                req.setCommand(msg.getMessageHeader().getCommand());
                JSONObject jsonObject = JSON.parseObject(JSONObject.toJSONString(msg.getMessagePack()));
                String fromId = jsonObject.getString("fromId");
                if (command == MessageCommand.MSG_P2P.getCommand()) {
                    toId = jsonObject.getString("toId");
                } else {
                    toId = jsonObject.getString("groupId");
                }
                req.setToId(toId);
                req.setFromId(fromId);
                // 调用 Feign 客户端接口，检查是否可以发送消息
                RestResponse response = feignMessageService.checkSendMessage(req);
                // 如果检查通过，则将消息发送到消息队列
                if (response.isStatus()) {
                    mqMessageProducer.sendMessage(msg, command);
                } else { // 否则，将检查结果返回给客户端
                    Integer ackCommand = 0;
                    if (command == MessageCommand.MSG_P2P.getCommand()) {
                        ackCommand = MessageCommand.MSG_ACK.getCommand();
                    } else {
                        ackCommand = GroupEventCommand.GROUP_MSG_ACK.getCommand();
                    }
                    // 创建 ChatMessageAck 对象，并将检查结果封装为 RestResponse 对象
                    ChatMessageAck chatMessageAck = new ChatMessageAck(jsonObject.getString("messageId"));
                    response.setData(chatMessageAck);
                    MessagePack<RestResponse> ack = new MessagePack<>();
                    ack.setData(response);
                    ack.setCommand(ackCommand);
                    // 将检查结果发送给客户端
                    ctx.channel().writeAndFlush(ack);
                }
            } catch (Exception e) {
                log.error("处理消息出现异常：{}", e.getMessage());
                e.printStackTrace();
            }
        } else {
            // 否则，将消息发送到消息队列
            mqMessageProducer.sendMessage(msg, command);
        }
    }

    //表示 channel 处于不活动状态
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        //设置离线
        SessionSocketHolder.offlineUserSession((NioSocketChannel) ctx.channel());
        ctx.close();
    }

}
